package wlsAgent;

import java.net.*;
import java.util.HashMap;
import java.util.Map;
import java.io.*;

import org.apache.log4j.Logger;

import wlsAgent.xml.IniXmlValidator;

public class FusionSpyWlsAgent extends ServerSocket {

	private final static int BUFFER_LEN = 2048;

	private boolean isAlive;

	private Map coreMap;

	private static Logger log;

	static {
		log = Logger.getLogger(wlsAgent.FusionSpyWlsAgent.class);
	}

	// Construct the listen thread
	public FusionSpyWlsAgent(int port) throws IOException {
		super(port);
		log.info("FusionSpyWlsAgent started at " + port + "...");
		isAlive = true;
		coreMap = new HashMap(6);
		coreMap.put("HANDINIT", "91");
		coreMap.put("HANDACK", "92");
		coreMap.put("GETSVRDATA", "93");
		coreMap.put("GETINITDATA", "94");
		coreMap.put("CLOSECONNECT", "95");
		coreMap.put("CLOSESVR", "96");
		try {

			while (isAlive) {
				// Blocks until a connection occurs:
				Socket socket = accept();
				handleIncomingSocket(socket);
			}
		} catch (IOException ioe) {
			log.error("ServerSocket got errors during accept process...", ioe);
		} finally {
			close();
			log.info("FusionSpyWlsAgent closed...");
		}

	}

	private void handleIncomingSocket(Socket socket) {
		try {
			PrintWriter pw = new PrintWriter(socket.getOutputStream());
			BufferedReader br = new BufferedReader(new InputStreamReader(socket
					.getInputStream()));

			char[] pubBuffer = new char[BUFFER_LEN];

			int len = br.read(pubBuffer, 0, 1);

			int iniRCode;

			if (len <= 0) {
				log
						.info("handleIncomingSocket: Cannot read any bytes from InputStream...");
			} else {
				iniRCode = (int) pubBuffer[0];
				if (iniRCode == Integer.valueOf((String) coreMap
						.get("HANDINIT"))) {

					pw.write((char) Integer.parseInt((String) coreMap
							.get("HANDACK")));
					pw.flush();
					new WlsAgentThread(socket, br, pw);
					log
							.info("handleIncomingSocket: Handshake with client confirmed, forked a new wlsAgentThread for data collection...");
				} else if (iniRCode == Integer.valueOf((String) coreMap
						.get("CLOSESVR"))) {
					this.isAlive = false;
					pw.close();
					br.close();
					socket.close();
					log
							.info("handleIncomingSocket: Close request confirmed, wlsagent process will exit...");
				}
			}
		} catch (IOException ioe) {
			log
					.error(
							"handleIncomingSocket: ServerSocket got errors while dealing with incoming socket...",
							ioe);
		}
	}

	class WlsAgentThread extends Thread {
		private Socket clientSkt;

		private PrintWriter out;

		private BufferedReader in;

		private char[] buffer;

		private boolean thrdIsAlive;

		private FusionStateCollector fsc;

		public WlsAgentThread(Socket s, BufferedReader br, PrintWriter pw)
				throws IOException {
			super("FusionSpy.WlsAgentThread");
			clientSkt = s;
			thrdIsAlive = true;
			out = pw;
			in = br;
			start(); // Calls run()
		}

		private void feedData(boolean isIni) {
			try {
				if (isIni && fsc == null) {

					fsc = new FusionStateCollector();
					// get ini xml length
					int len = in.read(buffer, 0, 8);

					// get ini xml
					String lenStr = new String(buffer, 0, len);
					int iniXmlLen = Integer.parseInt(lenStr.trim());
					log.info("ready to read " + iniXmlLen
							+ " bytes to generate ini-xml:");
					int readInLen = in.read(buffer, 0, iniXmlLen);

					// XML validation at pre-defined schema
					String iniXml = new String(buffer, 0, iniXmlLen);

					IniXmlValidator ixv = new IniXmlValidator();
					if (ixv.validateXMLByXSD(fsc.getCoreDoc())) {
						fsc
								.setCoreFields(FusionSpyUtil
										.string2Document(iniXml));
						boolean canConnect = fsc.initConnection();
						if (!canConnect)
							throw new FusionSpyException(
									"Can not connect to given domain!");
					}

					buffer = FusionSpyUtil.bytesToChars(fsc.genFeedData(true)
							.asXML().getBytes());
				} else {

					String feedStr = fsc.genFeedData(false).asXML();
					buffer = FusionSpyUtil.bytesToChars(feedStr.getBytes());
					log.debug(feedStr);
				}

				String sLen = String.valueOf(buffer.length);

				if (sLen.length() < 9) {
					int dif = 9 - sLen.length();
					StringBuffer zeroStr = new StringBuffer("");
					for (int m = 0; m < dif; m++) {
						zeroStr = zeroStr.append("0");
					}
					sLen = zeroStr.toString() + sLen;
				}

				char[] charLen = sLen.toCharArray();
				for (int i = 0; i < charLen.length; i++) {
					out.write(charLen[i]);
				}

				out.flush();
				log.debug("write feed-data's length into OutputStream...");
				out.write(buffer);
				out.flush();
				log.debug("write feed-data into OutputStream...");
			} catch (RuntimeException re) {
				log
						.error(
								"feedData: got RuntimeException while feeding date back to client...",
								re);
				this.thrdIsAlive = false;
			} catch (IOException ioe) {
				log
						.error(
								"feedData: got IOException while feeding date back to client...",
								ioe);
				this.thrdIsAlive = false;
			}
		}

		private void feedData() {
			feedData(false);
		}

		public void run() {
			try {
				log.info("A new WlsAgentThread started...");
				buffer = new char[BUFFER_LEN];

				while (thrdIsAlive && this.clientSkt.isConnected()) {

					int len = in.read(buffer, 0, 1);

					int rCode;

					if (len <= 0) {
						log.info("Cannot read any bytes from InputStream...");
						this.sleep(5000);
					} else {

						rCode = (int) buffer[0];

						switch (rCode) {

						case 93:
							log.debug("GETSVRDATA");
							feedData();
							break;

						case 94:
							log.debug("GETINITDATA");
							feedData(true);
							break;

						case 95:
							log.debug("CLOSECONNECT");
							thrdIsAlive = false;
							in.close();
							out.close();
							clientSkt.close();
							break;
						default:
							log.debug("NO PROPER CODE");
							thrdIsAlive = false;
							in.close();
							out.close();
							clientSkt.close();
							break;

						}
					}

					log.info("a collection loop done...");
				}
				this.fsc.closeCollector();
				log
						.info("Collection done , current WlsAgentThread will exit...");
			} catch (IOException ioe) {
				log.error(
						"Errors occured during WlsAgentThread run-circle ...",
						ioe);
			}catch (InterruptedException ie) {
				log.error(
						"Errors occured while WlsAgentThread sleeping  ...",
						ie);
			} finally {
				try {
					in.close();
					out.close();
					clientSkt.close();
					log.info("Client socket closed...");
				} catch (IOException ioe) {
					log
							.error(
									"Errors occured when try to close clent socket and its' streams ...",
									ioe);
				}
			}
		}

	}

	public static void main(String[] args) {
		try {
			FusionSpyUtil.initLog4j("wlsAgent/resource/log4j.properties");
			if (args.length != 0) {
				new FusionSpyWlsAgent(Integer.parseInt(args[0]));
			} else {
				new FusionSpyWlsAgent(8108);
			}
		} catch (IOException ioe) {
			log
					.error(
							"FusionSpyWlsAgent failed to start for following reasons: ",
							ioe);
		}
	}
}
